using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;

namespace Hsenl.Network {
    public abstract class IOCPSender : EpibolySocket, IIOCP, ISender {
        public int TcpSendBufferSizePer { get; set; } = 1024;
        public int UdpSendBufferSizePer { get; set; } = 1500;

        private readonly SocketAsyncEventArgs _sendEventArgs = new();

        private volatile int _isSending;
        private SocketBufferKind _bufferKind; // 有时候如果对于memory支持不友好的环境时, 可以使用array. 例如unity中, 对于memory的支持就不友好, 所以这只是权宜之计, 才增加了对byte[]的支持.

        public bool IsSending => this._isSending != 0;
        public SocketBufferKind BufferKind => this._bufferKind;

        protected IOCPSender(Socket socket, SocketBufferKind bufferKind) : base(socket) {
            this._sendEventArgs.Completed += this.SendEventArgs_OnCompleted;
            this._sendEventArgs.Completed += this.SendPacketsEventArgs_OnCompleted;
            this._sendEventArgs.Completed += this.SendToEventArgs_OnCompleted;
            this.Init(socket, bufferKind);
        }

        public void Init(Socket socket, SocketBufferKind bufferKind) {
            base.Init(socket);
            this._bufferKind = bufferKind;
        }

        // tcp
        protected override bool SendAsync(SocketFlags socketFlags = SocketFlags.None) {
            this.CheckDisposedException();

            if (this.CompareExchange()) {
                this._sendEventArgs.SocketFlags = socketFlags;
                this.SendAsync(this._sendEventArgs);
                return true;
            }

            return false;
        }

        // tcp
        // 适合需要高效发送多个缓冲区或文件数据的场景
        protected override bool SendPacketsAsync(IList<ArraySegment<byte>> buffers, SocketFlags socketFlags = SocketFlags.None) {
            this.CheckDisposedException();

            if (this.CompareExchange()) {
                this._sendEventArgs.BufferList = buffers;
                this._sendEventArgs.SocketFlags = socketFlags;
                this.SendPacketsAsync(this._sendEventArgs);
                return true;
            }

            return false;
        }

        // udp(发送必须指定一个地址)
        protected override bool SendToAsync(SocketFlags socketFlags = SocketFlags.None) {
            this.CheckDisposedException();

            if (this.CompareExchange()) {
                this._sendEventArgs.SocketFlags = socketFlags;
                this.SendToAsync(this._sendEventArgs);
                return true;
            }

            return false;
        }

        // 发送数据
        private void SendAsync(SocketAsyncEventArgs e) {
            while (true) {
                if (this.IsClosedOrDisposed()) {
                    this.StopSending();
                    return;
                }

                try {
                    switch (this._bufferKind) {
                        case SocketBufferKind.Memory:
                            var buffer = this.GetSendBuffer(this.TcpSendBufferSizePer);
                            if (buffer.Length == 0) {
                                this.StopSending();
                                return;
                            }

                            this.CheckGreaterThanTcpSendSizeException(buffer.Length);
                            e.SetBuffer(buffer);
                            break;
                        case SocketBufferKind.Array:
                            var bytes = this.GetSendBuffer(this.TcpSendBufferSizePer, out int offset, out int count);
                            if (count == 0 || bytes == null) {
                                this.StopSending();
                                return;
                            }

                            this.CheckGreaterThanTcpSendSizeException(count);
                            e.SetBuffer(bytes, offset, count);
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }

                    if (!this.Socket.SendAsync(e)) {
                        this.ProcessSend(e);
                    }
                    else {
                        break;
                    }
                }
                catch (SocketException exception) {
                    this.StopSending();
                    this.Error(exception.ErrorCode);
                }
                catch {
                    this.StopSending();
                    throw;
                }
            }
        }

        private void SendEventArgs_OnCompleted(object sender, SocketAsyncEventArgs e) {
            if (e.LastOperation != SocketAsyncOperation.Send)
                return;

            this.ProcessSend(e);

            if (e.SocketError == SocketError.Success) {
                if (e.BytesTransferred != 0) {
                    this.SendAsync(e);
                }
            }
            else {
                this.StopSending();
                this.Error((int)e.SocketError);
            }
        }

        private void ProcessSend(SocketAsyncEventArgs e) {
            if (e.SocketError == SocketError.Success) {
                if (e.BytesTransferred != 0) {
                    switch (this._bufferKind) {
                        case SocketBufferKind.Memory:
                            var data = e.MemoryBuffer.Slice(0, e.BytesTransferred);
                            try {
                                this.OnSend(data);
                            }
                            catch (Exception exception) {
                                Log.Error(exception);
                            }

                            break;
                        case SocketBufferKind.Array:
                            try {
                                this.OnSend(e.Buffer, e.Offset, e.BytesTransferred);
                            }
                            catch (Exception exception) {
                                Log.Error(exception);
                            }

                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }
            }
        }

        // 发送数据packets
        private void SendPacketsAsync(SocketAsyncEventArgs e) {
            while (true) {
                if (this.IsClosedOrDisposed()) {
                    this.StopSending();
                    return;
                }

                try {
                    if (!this.Socket.SendPacketsAsync(e)) {
                        this.ProcessSendPackets(e);
                    }
                    else {
                        break;
                    }
                }
                catch (SocketException exception) {
                    this.StopSending();
                    this.Error(exception.ErrorCode);
                }
                catch {
                    this.StopSending();
                    throw;
                }
            }
        }

        private void SendPacketsEventArgs_OnCompleted(object sender, SocketAsyncEventArgs e) {
            if (e.LastOperation != SocketAsyncOperation.SendPackets)
                return;

            this.ProcessSendPackets(e);

            if (e.SocketError == SocketError.Success) {
                if (e.BytesTransferred != 0) { }
            }
            else {
                this.StopSending();
                this.Error((int)e.SocketError);
            }

            this.StopSending();
        }

        private void ProcessSendPackets(SocketAsyncEventArgs e) { }

        // 发送数据to
        private void SendToAsync(SocketAsyncEventArgs e) {
            while (true) {
                if (this.IsClosedOrDisposed()) {
                    this.StopSending();
                    return;
                }

                try {
                    switch (this._bufferKind) {
                        case SocketBufferKind.Memory: {
                            var buffer = this.GetSendToBuffer(this.UdpSendBufferSizePer, out var remoteEndPoint);
                            if (buffer.Length == 0) {
                                this.StopSending();
                                return;
                            }

                            this.CheckGreaterThanUdpSendSizeException(buffer.Length);
                            e.RemoteEndPoint = remoteEndPoint;
                            e.SetBuffer(buffer);
                            break;
                        }
                        case SocketBufferKind.Array: {
                            var bytes = this.GetSendToBuffer(this.UdpSendBufferSizePer, out int offset, out int count, out var remoteEndPoint);
                            if (count == 0 || bytes == null) {
                                this.StopSending();
                                return;
                            }

                            this.CheckGreaterThanUdpSendSizeException(count);
                            e.RemoteEndPoint = remoteEndPoint;
                            e.SetBuffer(bytes, offset, count);
                            break;
                        }
                        default:
                            throw new ArgumentOutOfRangeException();
                    }

                    if (!this.Socket.SendToAsync(e)) {
                        this.ProcessSendTo(e);
                    }
                    else {
                        break;
                    }
                }
                catch (SocketException exception) {
                    this.StopSending();
                    this.Error(exception.ErrorCode);
                }
                catch {
                    this.StopSending();
                    throw;
                }
            }
        }

        private void SendToEventArgs_OnCompleted(object sender, SocketAsyncEventArgs e) {
            if (e.LastOperation != SocketAsyncOperation.SendTo)
                return;

            this.ProcessSendTo(e);

            if (e.SocketError == SocketError.Success) {
                if (e.BytesTransferred != 0) {
                    this.SendToAsync(e);
                }
            }
            else {
                this.StopSending();
                this.Error((int)e.SocketError);
            }
        }

        private void ProcessSendTo(SocketAsyncEventArgs e) {
            if (e.SocketError == SocketError.Success) {
                if (e.BytesTransferred != 0) {
                    switch (this._bufferKind) {
                        case SocketBufferKind.Memory:
                            var data = e.MemoryBuffer.Slice(0, e.BytesTransferred);
                            try {
                                this.OnSendTo(e.RemoteEndPoint, data);
                            }
                            catch (Exception exception) {
                                Log.Error(exception);
                            }

                            break;
                        case SocketBufferKind.Array:
                            try {
                                this.OnSendTo(e.RemoteEndPoint, e.Buffer, e.Offset, e.BytesTransferred);
                            }
                            catch (Exception exception) {
                                Log.Error(exception);
                            }

                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }
            }
        }

        protected abstract Memory<byte> GetSendBuffer(int length);
        protected abstract Memory<byte> GetSendToBuffer(int length, out EndPoint remoteEndPoint);
        protected abstract byte[] GetSendBuffer(int length, out int offset, out int count);
        protected abstract byte[] GetSendToBuffer(int length, out int offset, out int count, out EndPoint remoteEndPoint);

        protected virtual void OnSend(Memory<byte> data) { }
        protected virtual void OnSend(byte[] data, int offset, int count) { }

        protected virtual void OnSendTo(EndPoint remoteEndPoint, Memory<byte> data) { }
        protected virtual void OnSendTo(EndPoint remoteEndPoint, byte[] data, int offset, int count) { }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool CompareExchange() {
            return Interlocked.CompareExchange(ref this._isSending, 1, 0) == 0;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void StopSending() {
            Interlocked.Exchange(ref this._isSending, 0);
            // 我们无法直接的关闭 saea, 需要等到他 completed, 才算是关闭了
            if (this.IsClosed) {
                this.OnClosed();
            }
        }

        public override void Close(bool closeSocket = true) {
            if (this.IsClosed)
                return;

            base.Close(closeSocket);

            this._sendEventArgs.RemoteEndPoint = null;
            this._sendEventArgs.BufferList = null;
            this._sendEventArgs.UserToken = null;

            if (!this.IsSending) {
                this.OnClosed();
            }
        }

        public override void Dispose() {
            if (this.IsDisposed)
                return;

            base.Dispose();

            this._sendEventArgs.Completed -= this.SendEventArgs_OnCompleted;
            this._sendEventArgs.Completed -= this.SendPacketsEventArgs_OnCompleted;
            this._sendEventArgs.Completed -= this.SendToEventArgs_OnCompleted;
            this._sendEventArgs.RemoteEndPoint = null;
            this._sendEventArgs.BufferList = null;
            this._sendEventArgs.UserToken = null;
            this._sendEventArgs.Dispose();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool IsClosedOrDisposed() {
            return this.IsClosed || this.IsDisposed;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void CheckGreaterThanTcpSendSizeException(int len) {
            if (len > this.TcpSendBufferSizePer)
                throw new ArgumentOutOfRangeException($"SendBufferLength over maximum '{len}' '{this.TcpSendBufferSizePer}'");
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void CheckGreaterThanUdpSendSizeException(int len) {
            if (len > this.UdpSendBufferSizePer)
                throw new ArgumentOutOfRangeException($"SendBufferLength over maximum '{len}' '{this.UdpSendBufferSizePer}'");
        }
    }
}